package com.swak.fx.support.ability;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;

import javafx.beans.value.ChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.Pane;

/**
 * 装饰: 关键点在于 触发 layoutChildren， 后面在研究
 * 
 * @author 618lf
 */
@Deprecated
public class DecorateAbility {

	public static DecorateAbility of(Node targetNode, Node decorationNode, Pos position, double xOffset,
			double yOffset) {
		return new DecorateAbility(targetNode, decorationNode, position, xOffset, yOffset);
	}

	public static DecorateAbility of(Node targetNode, Node decorationNode, Pos pos) {
		return new DecorateAbility(targetNode, decorationNode, pos, 0, 0);
	}

	public static DecorateAbility of(Node targetNode, Node decorationNode) {
		return new DecorateAbility(targetNode, decorationNode, Pos.TOP_RIGHT, 0, 0);
	}

	private final Node targetNode;
	private final Node decorationNode;
	private final Pos pos;
	private final double xOffset;
	private final double yOffset;
	private final ChangeListener<Boolean> targetNeedsLayoutListener;

	public DecorateAbility(Node targetNode, Node decorationNode, Pos position, double xOffset, double yOffset) {
		this.targetNode = targetNode;
		this.decorationNode = decorationNode;
		this.decorationNode.setManaged(false);
		this.pos = position;
		this.xOffset = xOffset;
		this.yOffset = yOffset;
		this.targetNeedsLayoutListener = (__, ___, ____) -> Optional.ofNullable(decorationNode.getParent())
				.ifPresent(this::layoutGraphic);
		this.applyDecoration();
	}

	private void applyDecoration() {
		List<Node> targetNodeChildren = getChildren((Parent) targetNode);
		layoutGraphic(targetNode);
		if (!targetNodeChildren.contains(decorationNode)) {
			targetNodeChildren.add(decorationNode);
		}
		((Parent) targetNode).needsLayoutProperty().removeListener(targetNeedsLayoutListener); // In case already added.
		((Parent) targetNode).needsLayoutProperty().addListener(targetNeedsLayoutListener);
	}

	private void layoutGraphic(Node targetNode) {
		// Because we made decorationNode unmanaged, we are responsible for sizing it:
		this.decorationNode.autosize();

		// Now get decorationNode's layout Bounds and use for its position computations:
		final Bounds decorationNodeLayoutBounds = decorationNode.getLayoutBounds();
		final double decorationNodeWidth = decorationNodeLayoutBounds.getWidth();
		final double decorationNodeHeight = decorationNodeLayoutBounds.getHeight();

		Bounds targetBounds = targetNode.getLayoutBounds();
		double x = targetBounds.getMinX();
		double y = targetBounds.getMinY();

		double targetWidth = targetBounds.getWidth();
		if (targetWidth <= 0) {
			targetWidth = targetNode.prefWidth(-1);
		}

		double targetHeight = targetBounds.getHeight();
		if (targetHeight <= 0) {
			targetHeight = targetNode.prefHeight(-1);
		}

		// x
		switch (pos.getHpos()) {
		case CENTER:
			x += targetWidth / 2 - decorationNodeWidth / 2.0;
			break;
		case LEFT:
			x -= decorationNodeWidth / 2.0;
			break;
		case RIGHT:
			x += targetWidth - decorationNodeWidth / 2.0;
			break;
		}

		// y
		switch (pos.getVpos()) {
		case CENTER:
			y += targetHeight / 2 - decorationNodeHeight / 2.0;
			break;
		case TOP:
			y -= decorationNodeHeight / 2.0;
			break;
		case BOTTOM:
			y += targetHeight - decorationNodeHeight / 2.0;
			break;
		case BASELINE:
			y += targetNode.getBaselineOffset() - decorationNode.getBaselineOffset() - decorationNodeHeight / 2.0;
			break;
		}

		this.decorationNode.relocate(x + xOffset, y + yOffset);
	}

	private static List<Node> getChildren(Parent p) {
		ObservableList<Node> children = null;

		// previously we used reflection immediately, now we try to avoid reflection
		// by checking the type of the Parent. Still not great...
		if (p instanceof Pane) {
			// This should cover the majority of layout containers, including
			// AnchorPane, FlowPane, GridPane, HBox, Pane, StackPane, TilePane, VBox
			children = ((Pane) p).getChildren();
		} else if (p instanceof Group) {
			children = ((Group) p).getChildren();
		} else if (p instanceof Control) {
			Control c = (Control) p;
			Skin<?> s = c.getSkin();
			children = s instanceof SkinBase ? ((SkinBase<?>) s).getChildren() : getChildrenReflectively(p);
		}

		if (children == null) {
			throw new RuntimeException("Unable to get children for Parent of type " + p.getClass() + "."); //$NON-NLS-1$
		}
		return children;
	}

	@SuppressWarnings({ "unchecked" })
	private static ObservableList<Node> getChildrenReflectively(Parent p) {
		ObservableList<Node> children = null;

		try {
			Method getChildrenMethod = Parent.class.getDeclaredMethod("getChildren"); //$NON-NLS-1$

			if (getChildrenMethod != null) {
				if (!getChildrenMethod.canAccess(p)) {
					getChildrenMethod.setAccessible(true);
				}
				children = (ObservableList<Node>) getChildrenMethod.invoke(p);
			} else {
				// uh oh, trouble
			}
		} catch (ReflectiveOperationException | IllegalArgumentException e) {
			throw new RuntimeException("Unable to get children for Parent of type " + p.getClass(), e); //$NON-NLS-1$
		}

		return children;
	}
}